/**
 * Copyright 2004-present, Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ProcFs.h"

#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <array>
#include <climits>
#include <stdexcept>

namespace facebook {
namespace profilo {
namespace counters {

static constexpr int kMaxProcFileLength = 64;

TaskStatInfo getStatInfo(int32_t tid) {
  return TaskStatFile(tid).refresh();
}

ThreadStatInfo ThreadStatInfo::createThreadStatInfo(
    MultiBufferLogger& logger,
    int32_t tid) {
  return ThreadStatInfo{
      .cpuTimeMs =
          TraceCounter(logger, QuickLogConstants::THREAD_CPU_TIME, tid),
      .state = TraceCounter(logger, QuickLogConstants::THREAD_STATE, tid),
      .majorFaults =
          TraceCounter(logger, QuickLogConstants::QL_THREAD_FAULTS_MAJOR, tid),
      .cpuNum = TraceCounter(logger, QuickLogConstants::THREAD_CPU_NUM, tid),
      .kernelCpuTimeMs =
          TraceCounter(logger, QuickLogConstants::THREAD_KERNEL_CPU_TIME, tid),
      .minorFaults =
          TraceCounter(logger, QuickLogConstants::THREAD_SW_FAULTS_MINOR, tid),
      .threadPriority =
          TraceCounter(logger, QuickLogConstants::THREAD_PRIORITY, tid),
      .highPrecisionCpuTimeMs =
          TraceCounter(logger, QuickLogConstants::THREAD_CPU_TIME, tid),
      .waitToRunTimeMs = TraceCounter(
          logger, QuickLogConstants::THREAD_WAIT_IN_RUNQUEUE_TIME, tid),
      .nrVoluntarySwitches = TraceCounter(
          logger, QuickLogConstants::CONTEXT_SWITCHES_VOLUNTARY, tid),
      .nrInvoluntarySwitches = TraceCounter(
          logger, QuickLogConstants::CONTEXT_SWITCHES_INVOLUNTARY, tid),
      .iowaitSum = TraceCounter(logger, QuickLogConstants::IOWAIT_TIME, tid),
      .iowaitCount = TraceCounter(logger, QuickLogConstants::IOWAIT_COUNT, tid),
      .availableStatsMask = 0,
  };
}

SchedstatInfo::SchedstatInfo() : cpuTimeMs(0), waitToRunTimeMs(0) {}

SchedInfo::SchedInfo()
    : nrVoluntarySwitches(0),
      nrInvoluntarySwitches(0),
      iowaitSum(0),
      iowaitCount(0) {}

TaskStatInfo::TaskStatInfo()
    : cpuTime(0),
      state(ThreadState::TS_UNKNOWN),
      majorFaults(0),
      cpuNum(-1),
      kernelCpuTimeMs(0),
      minorFaults(0),
      threadPriority(999) {}

VmStatInfo::VmStatInfo()
    : nrFreePages(0),
      nrDirty(0),
      nrWriteback(0),
      pgPgIn(0),
      pgPgOut(0),
      pgMajFault(0),
      allocStall(0),
      pageOutrun(0),
      kswapdSteal(0) {}

namespace {

enum StatFileType : int8_t {
  STAT = 0,
  SCHEDSTAT = 1,
  SCHED = 1 << 1,
};

static const std::array<int32_t, 3> kFileStats = {{
    /*STAT*/ StatType::CPU_TIME | StatType::STATE | StatType::MAJOR_FAULTS |
        StatType::CPU_NUM | StatType::KERNEL_CPU_TIME | StatType::MINOR_FAULTS |
        StatType::THREAD_PRIORITY,
    /*SCHEDSTAT*/ StatType::HIGH_PRECISION_CPU_TIME |
        StatType::WAIT_TO_RUN_TIME,
    /*SCHED*/ StatType::NR_VOLUNTARY_SWITCHES |
        StatType::NR_INVOLUNTARY_SWITCHES | StatType::IOWAIT_SUM |
        StatType::IOWAIT_COUNT,
}};

inline ThreadState convertCharToStateEnum(char stateChar) {
  switch (stateChar) {
    case 'R':
      return TS_RUNNING;
    case 'S':
      return TS_SLEEPING;
    case 'D':
      return TS_WAITING;
    case 'Z':
      return TS_ZOMBIE;
    case 'T':
      return TS_STOPPED;
    case 't':
      return TS_TRACING_STOP;
    case 'X':
    case 'x':
      return TS_DEAD;
    case 'K':
      return TS_WAKEKILL;
    case 'W':
      return TS_WAKING;
    case 'P':
      return TS_PARKED;
  }
  return TS_UNKNOWN;
}

// Consumes data until `ch` or we reach `end`.
// Returns a pointer immediately after `ch`.
//
// Throws std::runtime_error if `end` is reached before `ch`
// or \0 is encountered anywhere in the string.
char* skipUntil(char* data, const char* end, char ch) {
  // It's important that we check against `end`
  // before we dereference `data`.
  while (data < end && *data != ch) {
    if (*data == '\0') {
      throw std::runtime_error("Unexpected end of string");
    }

    ++data;
  }

  if (data == end) {
    throw std::runtime_error("Unexpected end of string");
  }

  // One past the `ch` character.
  return ++data;
}

TaskStatInfo parseStatFile(char* data, size_t size, uint32_t stats_mask) {
  const char* end = (data + size);

  data = skipUntil(data, end, ' '); // pid
  data = skipUntil(data, end, ')'); // name
  data = skipUntil(data, end, ' '); // space after name
  char state = *data; // state

  data = skipUntil(data, end, ' '); // state

  data = skipUntil(data, end, ' '); // ppid
  data = skipUntil(data, end, ' '); // pgrp
  data = skipUntil(data, end, ' '); // session
  data = skipUntil(data, end, ' '); // tty_nr
  data = skipUntil(data, end, ' '); // tpgid

  data = skipUntil(data, end, ' '); // flags

  char* endptr = nullptr;
  auto minflt = parse_ull(data, &endptr); // minflt
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse minflt");
  }
  data = skipUntil(endptr, end, ' ');

  data = skipUntil(data, end, ' '); // cminflt

  endptr = nullptr;
  auto majflt = parse_ull(data, &endptr); // majflt
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse majflt");
  }
  data = skipUntil(endptr, end, ' ');

  data = skipUntil(data, end, ' '); // cmajflt

  endptr = nullptr;
  auto utime = parse_ull(data, &endptr); // utime
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse utime");
  }
  data = skipUntil(endptr, end, ' ');

  endptr = nullptr;
  auto stime = parse_ull(data, &endptr); // stime
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse stime");
  }
  data = skipUntil(endptr, end, ' ');

  data = skipUntil(data, end, ' '); // cutime
  data = skipUntil(data, end, ' '); // cstime

  endptr = nullptr;
  auto priority = strtol(data, &endptr, 10); // priority
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse priority");
  }

  int cpuNum = 0;
  if (StatType::CPU_NUM & stats_mask) {
    data = skipUntil(endptr, end, ' ');

    data = skipUntil(data, end, ' '); // nice
    data = skipUntil(data, end, ' '); // num_threads
    data = skipUntil(data, end, ' '); // itrealvalue
    data = skipUntil(data, end, ' '); // starttime

    data = skipUntil(data, end, ' '); // vsize
    data = skipUntil(data, end, ' '); // rss
    data = skipUntil(data, end, ' '); // rsslim
    data = skipUntil(data, end, ' '); // startcode
    data = skipUntil(data, end, ' '); // endcode
    data = skipUntil(data, end, ' '); // startstack
    data = skipUntil(data, end, ' '); // kstkesp
    data = skipUntil(data, end, ' '); // kstkeip

    data = skipUntil(data, end, ' '); // signal
    data = skipUntil(data, end, ' '); // blocked
    data = skipUntil(data, end, ' '); // sigignore
    data = skipUntil(data, end, ' '); // sigcatch
    data = skipUntil(data, end, ' '); // wchan
    data = skipUntil(data, end, ' '); // nswap
    data = skipUntil(data, end, ' '); // cnswap
    data = skipUntil(data, end, ' '); // exit_signal
    endptr = nullptr;
    cpuNum = strtol(data, &endptr, 10); // processor
    if (errno == ERANGE || data == endptr || endptr > end) {
      throw std::runtime_error("Could not parse cpu num");
    }
  }

  // SYSTEM_CLK_TCK is defined as 100 in linux as is unchanged in android.
  // Therefore there are 10 milli seconds in each clock tick.
  static int kClockTicksMs = systemClockTickIntervalMs();

  TaskStatInfo info{};
  info.cpuTime = kClockTicksMs * (utime + stime);
  info.kernelCpuTimeMs = kClockTicksMs * stime;
  info.state = convertCharToStateEnum(state);
  info.majorFaults = majflt;
  info.minorFaults = minflt;
  info.cpuNum = cpuNum;
  info.threadPriority = priority;

  return info;
}

std::string tidToStatPath(int32_t tid, const char* stat_name) {
  char threadStatPath[kMaxProcFileLength]{};

  int bytesWritten = snprintf(
      threadStatPath,
      kMaxProcFileLength,
      "/proc/self/task/%d/%s",
      tid,
      stat_name);

  if (bytesWritten < 0 || bytesWritten >= kMaxProcFileLength) {
    throw std::system_error(
        errno, std::system_category(), "Could not format file path");
  }
  return std::string(threadStatPath);
}

SchedstatInfo parseSchedstatFile(char* data, size_t size) {
  const char* end = (data + size);

  char* endptr = nullptr;
  auto run_time_ns = parse_ull(data, &endptr); // run time
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse run time");
  }
  data = skipUntil(endptr, end, ' ');
  endptr = nullptr;
  auto wait_time_ns = parse_ull(data, &endptr); // run time
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse wait time");
  }

  SchedstatInfo info{};
  info.cpuTimeMs = run_time_ns / 1000000;
  info.waitToRunTimeMs = wait_time_ns / 1000000;

  return info;
}

StatmInfo parseStatmFile(char* data, size_t size) {
  const char* end = (data + size);
  data = skipUntil(data, end, ' '); // size
  char* endptr = nullptr;
  auto resident = parse_ull(data, &endptr); // resident
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse resident");
  }
  data = skipUntil(endptr, end, ' ');
  endptr = nullptr;
  auto shared = parse_ull(data, &endptr); // shared
  if (errno == ERANGE || data == endptr || endptr > end) {
    throw std::runtime_error("Could not parse stime");
  }
  return (struct StatmInfo){.resident = resident, .shared = shared};
}

} // namespace

TaskStatFile::TaskStatFile(int32_t tid)
    : BaseStatFile<TaskStatInfo>(tidToStatPath(tid, "stat")) {}

TaskStatInfo TaskStatFile::doRead(int fd, uint32_t requested_stats_mask) {
  // This is a conservative upper bound, so we can read the
  // entire file in one fread call.
  constexpr size_t kMaxStatFileLength = 512;

  char buffer[kMaxStatFileLength]{};
  int bytes_read = read(fd, buffer, (sizeof(buffer) - 1));
  if (bytes_read < 0) {
    throw std::system_error(
        errno, std::system_category(), "Could not read stat file");
  }

  // At this point we know that `buffer` must be null terminated because we
  // zeroed the array before we read at most `sizeof(buffer) - 1` from the file.
  return parseStatFile(buffer, bytes_read, requested_stats_mask);
}

TaskSchedstatFile::TaskSchedstatFile(int32_t tid)
    : BaseStatFile<SchedstatInfo>(tidToStatPath(tid, "schedstat")) {}

SchedstatInfo TaskSchedstatFile::doRead(int fd, uint32_t requested_stats_mask) {
  // This is a conservative upper bound, so we can read the
  // entire file in one fread call.
  constexpr size_t kMaxStatFileLength = 128;

  char buffer[kMaxStatFileLength]{};
  int bytes_read = read(fd, buffer, (sizeof(buffer) - 1));
  if (bytes_read < 0) {
    throw std::system_error(
        errno, std::system_category(), "Could not read schedstat file");
  }

  // At this point we know that `buffer` must be null terminated because we
  // zeroed the array before we read at most `sizeof(buffer) - 1` from the file.
  return parseSchedstatFile(buffer, bytes_read);
}

TaskSchedFile::TaskSchedFile(int32_t tid)
    : BaseStatFile<SchedInfo>(tidToStatPath(tid, "sched")),
      value_offsets_(),
      initialized_(false),
      value_size_(),
      buffer_(),
      availableStatsMask(0) {}

SchedInfo TaskSchedFile::doRead(int fd, uint32_t requested_stats_mask) {
  int size = read(fd, buffer_, kMaxStatFileLength - 1);
  if (size < 0) {
    throw std::system_error(
        errno, std::system_category(), "Could not read stat file");
  }
  char* endfile = buffer_ + size;

  if (!initialized_) {
    struct KnownKey {
      const char* key;
      StatType type;
    };

    static std::array<KnownKey, 4> kKnownKeys = {
        {{"nr_voluntary_switches", StatType::NR_VOLUNTARY_SWITCHES},
         {"nr_involuntary_switches", StatType::NR_INVOLUNTARY_SWITCHES},
         {"se.statistics.iowait_count", StatType::IOWAIT_COUNT},
         {"se.statistics.iowait_sum", StatType::IOWAIT_SUM}}};

    // Skip 2 lines.
    auto endline = std::strchr(buffer_, '\n');
    if (endline == nullptr) {
      throw std::runtime_error("Unexpected file format");
    }
    endline = std::strchr(endline + 1, '\n');
    if (endline == nullptr) {
      throw std::runtime_error("Unexpected file format");
    }

    // The stat file line is in the form of key:value record with fixed line
    // length per metric, but which can vary depending on a metric name.
    // The key is aligned to the left and the value is aligned to the
    // right:  "key     :     value"
    // In the loop we parse the buffer line by line reading the key-value pairs.
    // If key is in the known keys (kKnownKeys) we calculate a global offset to
    // the value in the file and record it for fast access in the future.
    for (auto pos = endline + 1; pos < endfile;) {
      // Sometimes colon delimiter can go right after key ("key:") and we should
      // account for this case too.
      auto key_end = std::strchr(pos, ' ');
      auto delim = std::strchr(pos, ':');
      if (key_end == nullptr || delim == nullptr) {
        break;
      }
      auto key_len =
          std::min(std::distance(pos, key_end), std::distance(pos, delim));

      auto known_key = std::find_if(
          kKnownKeys.begin(),
          kKnownKeys.end(),
          [pos, key_len](KnownKey const& key) {
            return std::strncmp(key.key, pos, key_len) == 0;
          });
      if (known_key != kKnownKeys.end()) {
        int value_offset = std::distance(buffer_, delim) + 1;
        value_offsets_.push_back(std::make_pair(known_key->type, value_offset));
        availableStatsMask |= known_key->type;
      }
      // Switch to the next line.
      pos = std::strchr(delim, '\n');
      if (!pos) {
        break;
      }
      if (!value_size_) {
        // Saving allocated space for value (fixed size for all stats) to detect
        // truncated values.
        value_size_ = std::distance(delim, pos);
      }
      ++pos;
    }

    initialized_ = true;
  }

  if (value_offsets_.empty()) {
    throw std::runtime_error("No target fields found");
  }

  SchedInfo schedInfo{};
  for (auto& entry : value_offsets_) {
    auto key_type = entry.first;
    auto value_offset = entry.second;

    if (value_offset + value_size_ > size) {
      // Possibly truncated value, ignoring.
      continue;
    }
    errno = 0;
    char* endptr;
    auto value = parse_ull(buffer_ + value_offset, &endptr);
    if (errno == ERANGE || (buffer_ + value_offset) == endptr ||
        endptr > endfile) {
      throw std::runtime_error("Could not parse value");
    }

    switch (key_type) {
      case StatType::NR_VOLUNTARY_SWITCHES:
        schedInfo.nrVoluntarySwitches = value;
        break;
      case StatType::NR_INVOLUNTARY_SWITCHES:
        schedInfo.nrInvoluntarySwitches = value;
        break;
      case StatType::IOWAIT_COUNT:
        schedInfo.iowaitCount = value;
        break;
      case StatType::IOWAIT_SUM:
        schedInfo.iowaitSum = value;
        break;
    }
  }

  return schedInfo;
}

VmStatFile::VmStatFile(std::string path)
    : OrderedKeyedStatFile(
          path,

          // The order corresponds to the order in /proc/vmstat generated by the
          // Linux kernel
          {{"nr_free_pages",
            sizeof("nr_free_pages") - 1,
            kNotSet,
            &VmStatInfo::nrFreePages},
           {"nr_dirty", sizeof("nr_dirty") - 1, kNotSet, &VmStatInfo::nrDirty},
           {"nr_writeback",
            sizeof("nr_writeback") - 1,
            kNotSet,
            &VmStatInfo::nrWriteback},
           {"pgpgin", sizeof("pgpgin") - 1, kNotSet, &VmStatInfo::pgPgIn},
           {"pgpgout", sizeof("pgpgout") - 1, kNotSet, &VmStatInfo::pgPgOut},
           {"pgmajfault",
            sizeof("pgmajfault") - 1,
            kNotSet,
            &VmStatInfo::pgMajFault},
           // On latest kernel versions "kswapd_steal" was split by zones and
           // became: "pgsteal_kswapd_dma" + "pgsteal_kswapd_normal" +
           // "pgsteal_kswapd_movable"
           {"pgsteal_kswapd_dma",
            sizeof("pgsteal_kswapd_dma") - 1,
            kNotSet,
            &VmStatInfo::kswapdSteal},
           {"pgsteal_kswapd_normal",
            sizeof("pgsteal_kswapd_normal") - 1,
            kNotSet,
            &VmStatInfo::kswapdSteal},
           {"pgsteal_kswapd_movable",
            sizeof("pgsteal_kswapd_movable") - 1,
            kNotSet,
            &VmStatInfo::kswapdSteal},
           {"kswapd_steal",
            sizeof("kswapd_steal") - 1,
            kNotSet,
            &VmStatInfo::kswapdSteal},
           {"pageoutrun",
            sizeof("pageoutrun") - 1,
            kNotSet,
            &VmStatInfo::pageOutrun},
           {"allocstall",
            sizeof("allocstall") - 1,
            kNotSet,
            &VmStatInfo::allocStall}}) {}

VmStatFile::VmStatFile() : VmStatFile("/proc/vmstat") {}

MeminfoFile::MeminfoFile(std::string path)
    : OrderedKeyedStatFile(
          path,

          // The order corresponds to the order in /proc/meminfo generated by
          // the Linux kernel
          {
              {"MemFree:",
               sizeof("MemFree:") - 1,
               kNotSet,
               &MeminfoInfo::freeKB},
              {"Cached:",
               sizeof("Cached:") - 1,
               kNotSet,
               &MeminfoInfo::cachedKB},
              {"Active:",
               sizeof("Active:") - 1,
               kNotSet,
               &MeminfoInfo::activeKB},
              {"Inactive:",
               sizeof("Inactive:") - 1,
               kNotSet,
               &MeminfoInfo::inactiveKB},
              {"Dirty:", sizeof("Dirty:") - 1, kNotSet, &MeminfoInfo::dirtyKB},
              {"Writeback:",
               sizeof("Writeback:") - 1,
               kNotSet,
               &MeminfoInfo::writebackKB},
          }) {}

MeminfoFile::MeminfoFile() : MeminfoFile("/proc/meminfo") {}

StatmInfo ProcStatmFile::doRead(int fd, uint32_t requested_stats_mask) {
  // This is a conservative upper bound, so we can read the
  // entire file in one fread call.
  constexpr size_t kMaxStatFileLength = 64;

  char buffer[kMaxStatFileLength]{};
  int bytes_read = read(fd, buffer, (sizeof(buffer) - 1));
  if (bytes_read < 0) {
    throw std::system_error(
        errno, std::system_category(), "Could not read statm file");
  }

  // At this point we know that `buffer` must be null terminated because we
  // zeroed the array before we read at most `sizeof(buffer) - 1` from the file.
  return parseStatmFile(buffer, bytes_read);
}

ThreadStatHolder::ThreadStatHolder(MultiBufferLogger& logger, int32_t tid)
    : stat_file_(),
      schedstat_file_(),
      sched_file_(),
      last_info_(ThreadStatInfo::createThreadStatInfo(logger, tid)),
      availableStatFilesMask_(0xff),
      availableStatsMask_(0),
      tid_(tid) {}

void ThreadStatHolder::sampleAndLog(
    uint32_t requested_stats_mask,
    int32_t tid) {
  int64_t timestamp = monotonicTime();
  // If /proc/self/<tid>/schedstat is requested, we will try to read it.
  // If we get exception on first read the availableStatFilesMask will be
  // updated respectively. The second time this stat file will be ignored.
  if ((availableStatFilesMask_ & StatFileType::SCHEDSTAT) &&
      (kFileStats[StatFileType::SCHEDSTAT] & requested_stats_mask)) {
    if (schedstat_file_.get() == nullptr) {
      schedstat_file_ = std::make_unique<TaskSchedstatFile>(tid_);
    }
    try {
      auto schedstatInfo = schedstat_file_->refresh(requested_stats_mask);
      last_info_.waitToRunTimeMs.record(
          schedstatInfo.waitToRunTimeMs, timestamp);
      last_info_.highPrecisionCpuTimeMs.record(
          schedstatInfo.cpuTimeMs, timestamp);
      availableStatsMask_ |= kFileStats[StatFileType::SCHEDSTAT];
    } catch (const std::system_error& e) {
      // If 'schedstat' file is absent do not attempt the second time
      availableStatFilesMask_ ^= StatFileType::SCHEDSTAT;
      schedstat_file_.reset(nullptr);
    }
  }
  // Assuming that /proc/self/<tid>/stat is always available.
  if (kFileStats[StatFileType::STAT] & requested_stats_mask) {
    if (stat_file_.get() == nullptr) {
      stat_file_ = std::make_unique<TaskStatFile>(tid_);
    }
    auto statInfo = stat_file_->refresh(requested_stats_mask);
    if (!(availableStatsMask_ & StatType::HIGH_PRECISION_CPU_TIME)) {
      last_info_.cpuTimeMs.record(statInfo.cpuTime, timestamp);
    }
    last_info_.state.record(statInfo.state, timestamp);
    last_info_.cpuNum.record(statInfo.cpuNum, timestamp);
    last_info_.kernelCpuTimeMs.record(statInfo.kernelCpuTimeMs, timestamp);
    last_info_.majorFaults.record(statInfo.majorFaults, timestamp);
    last_info_.minorFaults.record(statInfo.minorFaults, timestamp);
    last_info_.threadPriority.record(statInfo.threadPriority, timestamp);
  }
  // If /proc/self/<tid>/sched is requested, we will try to read it.
  // If we get exception on first read the availableStatFilesMask will be
  // updated respectively. The second time this stat file will be ignored.
  if ((availableStatFilesMask_ & StatFileType::SCHED) &&
      (kFileStats[StatFileType::SCHED] & requested_stats_mask)) {
    if (sched_file_.get() == nullptr) {
      sched_file_ = std::make_unique<TaskSchedFile>(tid_);
    }
    try {
      auto schedInfo = sched_file_->refresh(requested_stats_mask);
      last_info_.nrVoluntarySwitches.record(
          schedInfo.nrVoluntarySwitches, timestamp);
      last_info_.nrInvoluntarySwitches.record(
          schedInfo.nrInvoluntarySwitches, timestamp);
      last_info_.iowaitSum.record(schedInfo.iowaitSum, timestamp);
      last_info_.iowaitCount.record(schedInfo.iowaitCount, timestamp);
      availableStatsMask_ |= sched_file_->availableStatsMask;
    } catch (const std::exception& e) {
      // If 'schedstat' file is absent do not attempt the second time
      availableStatFilesMask_ ^= StatFileType::SCHED;
      schedstat_file_.reset(nullptr);
    }
  }
  last_info_.availableStatsMask = availableStatsMask_;
}

ThreadStatInfo& ThreadStatHolder::getInfo() {
  return last_info_;
}

ThreadCache::ThreadCache(MultiBufferLogger& logger) : logger_(logger) {}

void ThreadCache::sampleAndLogForEach(
    uint32_t requested_stats_mask,
    const std::unordered_set<int32_t>* black_list) {
  try {
    const auto& threads = threadListFromProcFs();

    // Delete cached data for gone threads.
    for (auto iter = cache_.begin(); iter != cache_.end();) {
      if (threads.find(iter->first) == threads.end()) {
        iter = cache_.erase(iter);
      } else {
        ++iter;
      }
    }

    for (auto tid : threads) {
      if (black_list != nullptr && black_list->find(tid) != black_list->end()) {
        continue;
      }
      sampleAndLogForThread(tid, requested_stats_mask);
    }
  } catch (const std::system_error& e) {
    // threadListFromProcFs can throw an error. Ignore it.
    return;
  }
}

void ThreadCache::sampleAndLogForThread(
    int32_t tid,
    uint32_t requested_stats_mask) {
  auto statIter = cache_.find(tid);
  if (statIter == cache_.end()) {
    cache_.emplace(std::make_pair(tid, ThreadStatHolder(logger_, tid)));
  }
  auto& statHolder = cache_.at(tid);
  try {
    statHolder.sampleAndLog(requested_stats_mask, tid);
  } catch (const std::system_error&) {
  } catch (const std::runtime_error&) {
  }
  return;
}

int32_t ThreadCache::getStatsAvailabililty(int32_t tid) {
  int32_t stats_mask = 0;
  if (cache_.find(tid) != cache_.end()) {
    stats_mask = cache_.at(tid).getInfo().availableStatsMask;
  }
  return stats_mask;
}

void ThreadCache::clear() {
  cache_.clear();
}

} // namespace counters
} // namespace profilo
} // namespace facebook
